/* Copyright (c) 2008 LepomisLab.com, Andrey Turkin
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "NSDictionary.h"
#include <map>
#include <vector>

IMPLEMENT_DYNAMIC_CLASS(NSObject, wxObject)

NSObject::NSObject() : wxObject()
{
	_observers  = new kvo_map();
	_bindings = new bindings_map();
	_manual_observers = new manual_observers_set();
}

void NSObject::setValue(const wxString& key, wxObject *value)
{
	bool manual = _manual_observers->find(key) != _manual_observers->end();
	if (!manual)
		_willValueChangedForKey(key);
	_setValue(key, value);
	if (!manual)
		_didValueChangedForKey(key);
}

void NSObject::setValueForKeypath(const wxString& keypath, wxObject *value)
{
	NSObject *obj = this;
	size_t start = 0, i = keypath.find('.');
	while(i!=wxString::npos)
	{
		wxObject *o = obj->getValue(keypath.substr(0, i));
		wxASSERT(o);
		obj = wxDynamicCast(o, NSObject);
		wxASSERT(o);
		start = i+1;
		i = keypath.find('.', start);
	}
	obj->setValue(keypath.substr(start), value);
}

wxObject* NSObject::getValue(const wxString& key)
{
	return _getValue(key);
}

wxObject* NSObject::getValueForKeypath(const wxString& keypath)
{
	NSObject *obj = this;
	size_t start = 0, i = keypath.find('.');
	while(i!=wxString::npos)
	{
		wxObject *o = obj->getValue(keypath.substr(0, i));
		if (!o)
			return NULL;
		obj = wxDynamicCast(o, NSObject);
		if (!obj)
			return NULL;
		start = i+1;
		i = keypath.find('.', start);
	}
	return obj->getValue(keypath.substr(start));
}


class __NS_KVO_Proxy : public NSObject
{
public:
	__NS_KVO_Proxy(const wxString& keypath, NSObject* object, NSObject* observer, void* context) : NSObject(),
					_object(object), _observer(observer), _context(context)
	{
		_keypath = keypath;
	}
	__NS_KVO_Proxy(const __NS_KVO_Proxy& arg)
	{
		_keypath = arg._keypath;
		_object = arg._object;
		_observer = arg._observer;
		_context = arg._context;
	}

	virtual void observeValueForKeypath(const wxString &keypath, NSObject* object, void *context)
	{
		NSUNUSED(object);
		NSUNUSED(context);
		NSUNUSED(keypath);
		sendNotification();
	}
	const wxString& keypath(void) const { return _keypath; }
	void sendNotification(void)
	{
		wxString keypath(_keypath); // we can get deleted during callback so save string here
		_sendNotification(keypath, _observer, _object, _context);
	}
	static void _sendNotification(const wxString& str, NSObject *o, NSObject *obj, void *cont)
	{
		o->observeValueForKeypath(str, obj, cont);
	}


private:
	NSObject *_object, *_observer;
	wxString _keypath;
	void *_context;

};

void NSObject::registerObserver(NSObject *observer, const wxString& keypath, int options, void *context)
{
	NSUNUSED(options);
	size_t dot = keypath.find('.');
	wxString key, remainingKeypath;
	if (dot==wxString::npos)
	{
		key = keypath;
	} else {
		key = keypath.substr(0, dot);
		remainingKeypath = keypath.substr(dot+1);
	}

	kvo_map::const_iterator i = _observers->find(key);
	observers_map *m;
	if (i==_observers->end())
	{
		m = new observers_map();
		_observers->insert(kvo_map::value_type(key, m));
	} else {
		m = i->second;
	}

	observers_map::const_iterator j = m->find(observer);
	wxASSERT (j==m->end());
	__NS_KVO_Proxy* info = new __NS_KVO_Proxy(keypath, this, observer, context);
	m->insert(observers_map::value_type(observer, info));

	if (remainingKeypath.length()>0)
	{
		NSObject *o = wxDynamicCast(getValue(key), NSObject);
		if (o) o->registerObserver(info, remainingKeypath);
	}
}

void NSObject::removeObserver(NSObject *observer, const wxString& keypath)
{
	size_t dot = keypath.find('.');
	wxString key, remainingKeypath;
	if (dot==wxString::npos)
	{
		key = keypath;
	} else {
		key = keypath.substr(0, dot);
		remainingKeypath = keypath.substr(dot+1);
	}

	kvo_map::const_iterator i = _observers->find(key);
	if (i==_observers->end())
		return;
	observers_map *m = i->second;
	observers_map::iterator j = m->find(observer);
	if (j==m->end())
		return;
	__NS_KVO_Proxy* info = j->second;
	m->erase(j);

	if (remainingKeypath.length()>0)
	{
		NSObject *o = wxDynamicCast(getValue(key), NSObject);
		if (o)
			o->removeObserver(info, remainingKeypath);
	}
	delete info;
}

void NSObject::observeValueForKeypath(const wxString &keypath, NSObject* object, void *context)
{
	NSUNUSED(keypath);
	NSUNUSED(object);
	NSUNUSED(context);
//	wxASSERT(0);
}

void NSObject::_willValueChangedForKey(const wxString& key)
{
	kvo_map::const_iterator i = _observers->find(key);
	if (i != _observers->end())
	{
		observers_map *m = i->second;
		for(observers_map::iterator j=m->begin();j!=m->end();j++)
		{
			__NS_KVO_Proxy* info = j->second;
			size_t dot = info->keypath().find('.');
			if (dot!=wxString::npos)
			{
				wxASSERT(key == info->keypath().substr(0, dot));
				NSObject *o = wxDynamicCast(getValue(key), NSObject);
				if (o)
					o->removeObserver(info, info->keypath().substr(dot+1));
			}
		}
	}
}

typedef std::vector<__NS_KVO_Proxy*> infos_array;
void NSObject::_didValueChangedForKey(const wxString& key)
{
	kvo_map::const_iterator i = _observers->find(key);
	if (i != _observers->end())
	{
		infos_array infos; // we need to collect pending signals here because callbacks may perform any changes over observe structures
		observers_map *m = i->second;
		observers_map::iterator j=m->begin();
		while(j!=m->end())
//		for(observers_map::iterator j=m->begin();j!=m->end();++j)
		{
			__NS_KVO_Proxy* info = j->second;
			size_t dot = info->keypath().find('.');
			if (dot!=wxString::npos)
			{
				wxASSERT(key == info->keypath().substr(0, dot));
				NSObject *o = wxDynamicCast(getValue(key), NSObject);
				if (o) o->registerObserver(info, info->keypath().substr(dot+1));
			}
			infos.push_back(infos_array::value_type(new __NS_KVO_Proxy(*info)));
			++j;
		}
		for(infos_array::const_iterator k = infos.begin();k!=infos.end();++k)
		{
			__NS_KVO_Proxy *info = *k;
			info->sendNotification();
			delete info;
		}
	}
}

void NSObject::disableAutomaticObservation(const wxString &key)
{
	_manual_observers->insert(manual_observers_set::value_type(key));
}


void NSObject::_setValue(const wxString& key, wxObject *value)
{
	NSUNUSED(key);
	NSUNUSED(value);
	wxASSERT(false); // not implemented key
}

wxObject* NSObject::_getValue(const wxString& key)
{
	NSUNUSED(key);
	wxASSERT(false); // not implemented key
	return NULL;
}

#define SRC_O ((void*)1)
#define DST_O ((void*)2)

class NS_Binding : NSObject
{
public:
	NS_Binding(NSObject *src, const wxString& bind, NSObject *dest, const wxString& keypath, NSDictionary* options):
				NSObject(), _src(src), _bind(bind), _dest(dest), _keypath(keypath), _options(options)
	{
		updateObject();
		_do_bind();
	}
	~NS_Binding()
	{
		_do_unbind();
	}

	virtual void observeValueForKeypath(const wxString &keypath, NSObject* object, void *context)
	{
		_do_unbind();
		if (context == DST_O)
		{
			wxASSERT(keypath==_keypath && object == _dest);
			updateObject();
		} else
		{
			wxASSERT(context == SRC_O);
			wxASSERT(keypath==_bind && object == _src);
			_dest->setValueForKeypath(_keypath, _src->getValue(_bind));
		}
		_do_bind();
	}

	wxObject *getDestValue()
	{
		return _dest->getValueForKeypath(_keypath);
	}

	void updateObject()
	{
		wxObject *value = getDestValue();
		if (!value) return; // FIXME - what to do?
		value = transform(value);
		_src->setValue(_bind, value);
	}

	wxObject *transform(wxObject* value)
	{
		if (value == NSNoSelectionMarker)
			return new wxVariant(wxT("No selection"));
		else if (value == NSMultipleValuesMarker)
			return new wxVariant(wxT("multiply selections"));
		else
			return value;
	}

	NSDictionary* options(void) { return _options;}

private:
	NSObject *_src, *_dest;
	wxString _bind, _keypath;
	NSDictionary* _options;

	void _do_bind(void)
	{
		_src->registerObserver(this, _bind, 0, SRC_O);
		_dest->registerObserver(this, _keypath, 0, DST_O);
	}
	void _do_unbind(void)
	{
		_src->removeObserver(this, _bind);
		_dest->removeObserver(this, _keypath);
	}
};

void NSObject::bind(const wxString& bind, NSObject* target, const wxString& keypath, NSObject* options)
{
	unbind(bind);
	NSDictionary *_opt;
	if (options)
	{
		_opt = wxDynamicCast(options, NSDictionary);
		wxASSERT(_opt);
	} else
		_opt = NULL;

	bindings_map::iterator i = _bindings->find(bind);
	wxCHECK_RET(i != _bindings->end(), wxT("Required binding isn't exposed"));
	i->second = new NS_Binding(this, bind, target, keypath, _opt);
}

void NSObject::unbind(const wxString& bind)
{
	bindings_map::iterator i = _bindings->find(bind);
	if (i != _bindings->end())
	{
		delete i->second;
		i->second = NULL;
	}
}

void NSObject::exposeBinding(const wxString& bind)
{
	bindings_map::iterator i = _bindings->find(bind);
	wxASSERT(i==_bindings->end());
	_bindings->insert(bindings_map::value_type(bind,NULL));
}

void NSObject::sendAction(NSObject* target, const wxString& action, wxObject* arg)
{
	target->_callAction(action, arg, this);
}

void NSObject::_callAction(const wxString& action, wxObject* arg, NSObject* caller)
{
	NSUNUSED(action);
	NSUNUSED(arg);
	NSUNUSED(caller);
	wxASSERT(false);
}

void NSObject::bindingInvokeAction(const wxString& targetKey, const wxString& argumentKey)
{
	bindings_map::iterator i = _bindings->find(targetKey);
	wxASSERT(i!=_bindings->end());
	NS_Binding *target_bind = i->second;
	i = _bindings->find(argumentKey);
	wxASSERT(i!=_bindings->end());
	NS_Binding *argument_bind = i->second;

	if (!target_bind)
		return;

	wxObject *o = target_bind->getDestValue();
	if (!o)
		return;
	NSObject* target = wxDynamicCast(o, NSObject);
	wxASSERT(target);

	NSDictionary* arg_opts = argument_bind->options();
	if (!arg_opts)
		return;
	o = arg_opts->getValue(wxT("NSSelectorName"));
	if (!o)
		return;
	wxVariant *v = wxDynamicCast(o, wxVariant);
	wxASSERT(v);
	wxString action(v->GetString());

	o = argument_bind ? argument_bind->getDestValue() : NULL;

	sendAction(target, action, o);
}

wxPoint NSObject::bounds2wxPoint(wxWindow *parent, const wxRect& bounds)
{
	wxASSERT(parent);
	return wxPoint(bounds.GetX(), parent->GetClientSize().GetHeight() - bounds.GetY() - bounds.GetHeight());
}

